﻿using dtPolyRef = System.UInt32;
using dtTileRef = System.UInt32;
using dtStatus = System.UInt32;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.IO;
using System;

namespace Recast
{
	public unsafe partial class RecastGlobal
	{
		static int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET';
		static int NAVMESHSET_VERSION = 1;

		[StructLayout(LayoutKind.Sequential)]
		public struct NavMeshSetHeader
		{
			public int magic;
			public int version;
			public int numTiles;
			public dtNavMeshParams @params;
		};

		[StructLayout(LayoutKind.Sequential)]
		public struct NavMeshTileHeader
		{
			public dtTileRef tileRef;
			public int dataSize;
		};

		/// These are just sample areas to use consistent values across the samples.
		/// The use should specify these base on his needs.
		public enum SamplePolyAreas
		{
			SAMPLE_POLYAREA_GROUND,
			SAMPLE_POLYAREA_WATER,
			SAMPLE_POLYAREA_ROAD,
			SAMPLE_POLYAREA_DOOR,
			SAMPLE_POLYAREA_GRASS,
			SAMPLE_POLYAREA_JUMP
		};
		public enum SamplePolyFlags
		{
			SAMPLE_POLYFLAGS_WALK = 0x01,       // Ability to walk (ground, grass, road)
			SAMPLE_POLYFLAGS_SWIM = 0x02,       // Ability to swim (water).
			SAMPLE_POLYFLAGS_DOOR = 0x04,       // Ability to move through doors.
			SAMPLE_POLYFLAGS_JUMP = 0x08,       // Ability to jump.
			SAMPLE_POLYFLAGS_DISABLED = 0x10,       // Disabled polygon
			SAMPLE_POLYFLAGS_ALL = 0xffff   // All abilities.
		};

		public enum SamplePartitionType
		{
			SAMPLE_PARTITION_WATERSHED,
			SAMPLE_PARTITION_MONOTONE,
			SAMPLE_PARTITION_LAYERS
		};

		const int MAX_CONVEXVOL_PTS = 12;

		[StructLayout(LayoutKind.Sequential)]
		public struct ConvexVolume
		{
			public fixed float verts[MAX_CONVEXVOL_PTS * 3];
			public float hmin, hmax;
			public int nverts;
			public int area;
		};

		[Serializable]
		[StructLayout(LayoutKind.Sequential)]
		public struct BuildSettings
		{
			// Cell size in world units
			public float cellSize;
			// Cell height in world units
			public float cellHeight;
			// Agent height in world units
			public float agentHeight;
			// Agent radius in world units
			public float agentRadius;
			// Agent max climb in world units
			public float agentMaxClimb;
			// Agent max slope in degrees
			public float agentMaxSlope;
			// Region minimum size in voxels.
			// regionMinSize = sqrt(regionMinArea)
			public float regionMinSize;
			// Region merge size in voxels.
			// regionMergeSize = sqrt(regionMergeArea)
			public float regionMergeSize;
			// Edge max length in world units
			public float edgeMaxLen;
			// Edge max error in voxels
			public float edgeMaxError;
			public float vertsPerPoly;
			// Detail sample distance in voxels
			public float detailSampleDist;
			// Detail sample max error in voxel heights.
			public float detailSampleMaxError;
			// Partition type, see SamplePartitionType
			public int partitionType;
			// Bounds of the area to mesh
			public fixed float navMeshBMin[3];
			public fixed float navMeshBMax[3];
			// Size of the tiles in voxels
			public float tileSize;

			public static BuildSettings Default => new BuildSettings()
			{
				cellSize = 0.3f,
				cellHeight = 0.2f,
				agentHeight = 2.0f,
				agentRadius = 0.6f,
				agentMaxClimb = 0.9f,
				agentMaxSlope = 45.0f,
				regionMinSize = 8,
				regionMergeSize = 20,
				edgeMaxLen = 12.0f,
				edgeMaxError = 1.3f,
				vertsPerPoly = 6.0f,
				detailSampleDist = 6.0f,
				detailSampleMaxError = 1.0f,
				partitionType = (int)SamplePartitionType.SAMPLE_PARTITION_WATERSHED,
			};
		};

		[StructLayout(LayoutKind.Sequential)]
		public struct rcChunkyTriMeshNode
		{
			public fixed float bmin[2];
			public fixed float bmax[2];
			public int i;
			public int n;
		};

		[StructLayout(LayoutKind.Sequential)]
		public struct rcChunkyTriMesh : IDisposable
		{
			public rcChunkyTriMeshNode* nodes;
			public int nnodes;
			public int* tris;
			public int ntris;
			public int maxTrisPerChunk;

			//inline rcChunkyTriMesh() : nodes(0), nnodes(0), tris(0), ntris(0), maxTrisPerChunk(0) { }
			//inline ~rcChunkyTriMesh() { delete[] nodes; delete[] tris; }
			public void Dispose()
			{
				rcFree(nodes);
				rcFree(tris);
			}
		}
		[StructLayout(LayoutKind.Sequential)]
		public struct BoundsItem
		{
			public fixed float bmin[2];
			public fixed float bmax[2];
			public int i;
		};

		static void calcExtends(BoundsItem* items, int nitems, int imin, int imax, float* bmin, float* bmax)
		{
			bmin[0] = items[imin].bmin[0];
			bmin[1] = items[imin].bmin[1];

			bmax[0] = items[imin].bmax[0];
			bmax[1] = items[imin].bmax[1];

			for (int i = imin + 1; i < imax; ++i)
			{
				ref BoundsItem it = ref items[i];
				if (it.bmin[0] < bmin[0]) bmin[0] = it.bmin[0];
				if (it.bmin[1] < bmin[1]) bmin[1] = it.bmin[1];

				if (it.bmax[0] > bmax[0]) bmax[0] = it.bmax[0];
				if (it.bmax[1] > bmax[1]) bmax[1] = it.bmax[1];
			}
		}

		static int longestAxis(float x, float y)
		{
			return y > x ? 1 : 0;
		}

		static void subdivide(BoundsItem* items, int nitems, int imin, int imax, int trisPerChunk,
		   ref int curNode, rcChunkyTriMeshNode* nodes, int maxNodes,
		  ref int curTri, int* outTris, int* inTris)
		{
			int inum = imax - imin;
			int icur = curNode;

			if (curNode >= maxNodes)
				return;

			rcChunkyTriMeshNode* node = &nodes[curNode++];

			if (inum <= trisPerChunk)
			{
				// Leaf
				calcExtends(items, nitems, imin, imax, node->bmin, node->bmax);

				// Copy triangles.
				node->i = curTri;
				node->n = inum;

				for (int i = imin; i < imax; ++i)
				{
					int* src = &inTris[items[i].i * 3];
					int* dst = &outTris[curTri * 3];
					curTri++;
					dst[0] = src[0];
					dst[1] = src[1];
					dst[2] = src[2];
				}
			}
			else
			{
				// Split
				calcExtends(items, nitems, imin, imax, node->bmin, node->bmax);

				int axis = longestAxis(node->bmax[0] - node->bmin[0],
					node->bmax[1] - node->bmin[1]);

				if (axis == 0)
				{
					// Sort along x-axis
					qsort(items + imin, inum, sizeof(BoundsItem), compareItemX);
				}
				else if (axis == 1)
				{
					// Sort along y-axis
					qsort(items + imin, inum, sizeof(BoundsItem), compareItemY);
				}

				int isplit = imin + inum / 2;

				// Left
				subdivide(items, nitems, imin, isplit, trisPerChunk, ref curNode, nodes, maxNodes, ref curTri, outTris, inTris);
				// Right
				subdivide(items, nitems, isplit, imax, trisPerChunk, ref curNode, nodes, maxNodes, ref curTri, outTris, inTris);

				int iescape = curNode - icur;
				// Negative index means escape.
				node->i = -iescape;
			}
		}

		static bool rcCreateChunkyTriMesh(float* verts, int* tris, int ntris,
			 int trisPerChunk, rcChunkyTriMesh* cm)
		{
			int nchunks = (ntris + trisPerChunk - 1) / trisPerChunk;

			cm->nodes = rcNewArray<rcChunkyTriMeshNode>(nchunks * 4, rcAllocHint.RC_ALLOC_PERM); //new rcChunkyTriMeshNode[nchunks * 4];
			if (null == cm->nodes)
				return false;

			cm->tris = (int*)dtAlloc(sizeof(int) * ntris * 3, dtAllocHint.DT_ALLOC_TEMP);// new int[ntris * 3];
			if (null == cm->tris)
				return false;

			cm->ntris = ntris;

			// Build tree
			BoundsItem* items = rcNewArray<BoundsItem>(ntris, rcAllocHint.RC_ALLOC_PERM); //new BoundsItem[ntris];
			if (null == items)
				return false;

			for (int i = 0; i < ntris; i++)
			{
				int* t = &tris[i * 3];
				ref BoundsItem it = ref items[i];
				it.i = i;
				// Calc triangle XZ bounds.
				it.bmin[0] = it.bmax[0] = verts[t[0] * 3 + 0];
				it.bmin[1] = it.bmax[1] = verts[t[0] * 3 + 2];
				for (int j = 1; j < 3; ++j)
				{
					float* v = &verts[t[j] * 3];
					if (v[0] < it.bmin[0]) it.bmin[0] = v[0];
					if (v[2] < it.bmin[1]) it.bmin[1] = v[2];

					if (v[0] > it.bmax[0]) it.bmax[0] = v[0];
					if (v[2] > it.bmax[1]) it.bmax[1] = v[2];
				}
			}

			int curTri = 0;
			int curNode = 0;
			subdivide(items, ntris, 0, ntris, trisPerChunk, ref curNode, cm->nodes, nchunks * 4, ref curTri, cm->tris, tris);

			//delete[] items;
			rcDelete(items);

			cm->nnodes = curNode;

			// Calc max tris per node.
			cm->maxTrisPerChunk = 0;
			for (int i = 0; i < cm->nnodes; ++i)
			{
				ref rcChunkyTriMeshNode node = ref cm->nodes[i];
				bool isLeaf = node.i >= 0;
				if (!isLeaf) continue;
				if (node.n > cm->maxTrisPerChunk)
					cm->maxTrisPerChunk = node.n;
			}

			return true;
		}

		static bool checkOverlapRect(float* amin, float* amax,
				 float* bmin, float* bmax)
		{
			bool overlap = true;
			overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
			overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
			return overlap;
		}

		int rcGetChunksOverlappingRect(rcChunkyTriMesh* cm,
			float* bmin, float* bmax,
			int* ids, int maxIds)
		{
			// Traverse tree
			int i = 0;
			int n = 0;
			while (i < cm->nnodes)
			{
				rcChunkyTriMeshNode* node = &cm->nodes[i];
				bool overlap = checkOverlapRect(bmin, bmax, node->bmin, node->bmax);
				bool isLeafNode = node->i >= 0;

				if (isLeafNode && overlap)
				{
					if (n < maxIds)
					{
						ids[n] = i;
						n++;
					}
				}

				if (overlap || isLeafNode)
					i++;
				else
				{
					int escapeIndex = -node->i;
					i += escapeIndex;
				}
			}

			return n;
		}

		static bool checkOverlapSegment(float* p, float* q,
				 float* bmin, float* bmax)
		{
			float EPSILON = 1e-6f;

			float tmin = 0;
			float tmax = 1;
			float* d = stackalloc float[2];
			d[0] = q[0] - p[0];
			d[1] = q[1] - p[1];

			for (int i = 0; i < 2; i++)
			{
				if (fabsf(d[i]) < EPSILON)
				{
					// Ray is parallel to slab. No hit if origin not within slab
					if (p[i] < bmin[i] || p[i] > bmax[i])
						return false;
				}
				else
				{
					// Compute intersection t value of ray with near and far plane of slab
					float ood = 1.0f / d[i];
					float t1 = (bmin[i] - p[i]) * ood;
					float t2 = (bmax[i] - p[i]) * ood;
					if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; }
					if (t1 > tmin) tmin = t1;
					if (t2 < tmax) tmax = t2;
					if (tmin > tmax) return false;
				}
			}
			return true;
		}

		int rcGetChunksOverlappingSegment(rcChunkyTriMesh* cm,
			float* p, float* q,
			int* ids, int maxIds)
		{
			// Traverse tree
			int i = 0;
			int n = 0;
			while (i < cm->nnodes)
			{
				rcChunkyTriMeshNode* node = &cm->nodes[i];
				bool overlap = checkOverlapSegment(p, q, node->bmin, node->bmax);
				bool isLeafNode = node->i >= 0;

				if (isLeafNode && overlap)
				{
					if (n < maxIds)
					{
						ids[n] = i;
						n++;
					}
				}

				if (overlap || isLeafNode)
					i++;
				else
				{
					int escapeIndex = -node->i;
					i += escapeIndex;
				}
			}

			return n;
		}

		static char* parseRow(char* buf, char* bufEnd, char* row, int len)
		{
			bool start = true;
			bool done = false;
			int n = 0;
			while (!done && buf < bufEnd)
			{
				char c = *buf;
				buf++;
				// multirow
				switch (c)
				{
					case '\\':
						break;
					case '\n':
						if (start) break;
						done = true;
						break;
					case '\r':
						break;
					case '\t':
					case ' ':
						if (start) break;
						row[n++] = c;
						if (n >= len - 1)
							done = true;
						break;
					// else falls through
					default:
						start = false;
						row[n++] = c;
						if (n >= len - 1)
							done = true;
						break;
				}
			}
			row[n] = '\0';
			return buf;
		}

		static int parseFace(char* row, int* data, int n, int vcnt)
		{
			int j = 0;
			while (*row != '\0')
			{
				// Skip initial white space
				while (*row != '\0' && (*row == ' ' || *row == '\t'))
					row++;
				char* s = row;
				// Find vertex delimiter and terminated the string there for conversion.
				while (*row != '\0' && *row != ' ' && *row != '\t')
				{
					if (*row == '/') *row = '\0';
					row++;
				}
				if (*s == '\0')
					continue;
				int vi = atoi(s);
				data[j++] = vi < 0 ? vi + vcnt : vi - 1;
				if (j >= n) return j;
			}
			return j;
		}

		public class rcMeshLoaderObj
		{
			public rcMeshLoaderObj()
			//                  :
			//      m_scale(1.0f),
			//m_verts(0),
			//m_tris(0),
			//m_normals(0),
			//m_vertCount(0),
			//m_triCount(0)
			{
				m_scale = 1.0f;
			}
			~rcMeshLoaderObj()
			{
				rcDelete(m_verts);
				rcDelete(m_normals);
				rcDelete(m_tris);
			}

			public bool load(string filename)
			{
				var text = File.ReadAllText(filename);
				char* buf = (char*)Unsafe.AsPointer(ref text.ToCharArray()[0]);
				int bufSize = text.Length;
				bool result = loadMeshObj(buf, bufSize);
				return result;
			}

			public bool loadMeshObj(char* meshData, long meshDataLen)
			{
				char* src = meshData;
				char* srcEnd = meshData + meshDataLen;
				char* row = stackalloc char[512];
				int* face = stackalloc int[32];
				float* xyz = stackalloc float[3]; // = 0, y = 0, z = 0;
				int nv;
				int vcap = 0;
				int tcap = 0;

				while (src < srcEnd)
				{
					// Parse one row
					row[0] = '\0';
					src = parseRow(src, srcEnd, row, 512 / sizeof(char));
					// Skip comments
					if (row[0] == '#') continue;
					if (row[0] == 'v' && row[1] != 'n' && row[1] != 't')
					{
						// Vertex pos
						sscanf(row + 1, xyz);
						addVertex(xyz[0], xyz[1], xyz[2], ref vcap);
					}
					if (row[0] == 'f')
					{
						// Faces
						nv = parseFace(row + 1, face, 32, m_vertCount);
						for (int i = 2; i < nv; ++i)
						{
							int a = face[0];
							int b = face[i - 1];
							int c = face[i];
							if (a < 0 || a >= m_vertCount || b < 0 || b >= m_vertCount || c < 0 || c >= m_vertCount)
								continue;
							addTriangle(a, b, c, ref tcap);
						}
					}
				}

				setMesh(m_verts, m_tris, m_vertCount, m_triCount);

				return true;
			}

			private void sscanf(char* input, float* args)
			{
				for (int i = 0; i < 3; i++)
				{
					while (input[0] == ' ')
						input++;
					string text = "";
					char* start = input;
					while (input[0] != ' ' && input[0] != '\0')
					{
						text += input[0];
						input++;
					}
					args[i] = float.Parse(text);
				}
			}

			public void setMesh(float* m_verts, int* m_tris, int m_vertCount, int m_triCount)
			{
				this.m_verts = m_verts;
				this.m_tris = m_tris;
				//this->m_normals = m_normals;
				this.m_vertCount = m_vertCount;
				this.m_triCount = m_triCount;

				// Calculate normals.
				m_normals = (float*)dtAlloc(sizeof(float) * m_triCount * 3, dtAllocHint.DT_ALLOC_PERM);// new float[m_triCount * 3];
				float* e0 = stackalloc float[3], e1 = stackalloc float[3];
				for (int i = 0; i < m_triCount * 3; i += 3)
				{
					float* v0 = &m_verts[m_tris[i] * 3];
					float* v1 = &m_verts[m_tris[i + 1] * 3];
					float* v2 = &m_verts[m_tris[i + 2] * 3];
					//float* e0 = stackalloc float[3], e1 = stackalloc float[3];
					memset(e0, 0, sizeof(float) * 3);
					memset(e1, 0, sizeof(float) * 3);
					for (int j = 0; j < 3; ++j)
					{
						e0[j] = v1[j] - v0[j];
						e1[j] = v2[j] - v0[j];
					}
					float* n = &m_normals[i];
					n[0] = e0[1] * e1[2] - e0[2] * e1[1];
					n[1] = e0[2] * e1[0] - e0[0] * e1[2];
					n[2] = e0[0] * e1[1] - e0[1] * e1[0];
					float d = sqrtf(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]);
					if (d > 0)
					{
						d = 1.0f / d;
						n[0] *= d;
						n[1] *= d;
						n[2] *= d;
					}
				}
			}

			public float* getVerts() { return m_verts; }
			public float* getNormals() { return m_normals; }
			public int* getTris() { return m_tris; }
			public int getVertCount() { return m_vertCount; }
			public int getTriCount() { return m_triCount; }

			public void addVertex(float x, float y, float z, ref int cap)
			{
				if (m_vertCount + 1 > cap)
				{
					cap = 0 == cap ? 8 : cap * 2;
					float* nv = rcNewArray<float>(cap * 3, rcAllocHint.RC_ALLOC_PERM);// new float[cap * 3];
					if (m_vertCount != 0)
						memcpy(nv, m_verts, m_vertCount * 3 * sizeof(float));
					rcDelete(m_verts);
					m_verts = nv;
				}
				float* dst = &m_verts[m_vertCount * 3];
				*dst++ = x * m_scale;
				*dst++ = y * m_scale;
				*dst++ = z * m_scale;
				m_vertCount++;
			}
			void addTriangle(int a, int b, int c, ref int cap)
			{
				if (m_triCount + 1 > cap)
				{
					cap = 0 == cap ? 8 : cap * 2;
					int* nv = rcNewArray<int>(cap * 3, rcAllocHint.RC_ALLOC_PERM);
					if (m_triCount != 0)
						memcpy(nv, m_tris, m_triCount * 3 * sizeof(int));
					rcDelete(m_tris);
					m_tris = nv;
				}
				int* dst = &m_tris[m_triCount * 3];
				*dst++ = a;
				*dst++ = b;
				*dst++ = c;
				m_triCount++;
			}

			float m_scale;
			float* m_verts;
			int* m_tris;
			float* m_normals;
			int m_vertCount;
			int m_triCount;
		};

		public unsafe class InputGeom
		{
			private rcMeshLoaderObj _mesh;

			public rcChunkyTriMesh* m_chunkyMesh;
			public rcMeshLoaderObj* m_mesh
			{
				get => _mesh == null ? null : (rcMeshLoaderObj*)Unsafe.AsPointer(ref _mesh);
				set => _mesh = value != null ? *value : null;
			}
			public float* m_meshBMin/*[3]*/, m_meshBMax/*[3]*/;
			public BuildSettings* m_buildSettings;
			public bool m_hasBuildSettings;

			/// @name Off-Mesh connections.
			///@{
			public static int MAX_OFFMESH_CONNECTIONS = 256;
			public float* m_offMeshConVerts;// [MAX_OFFMESH_CONNECTIONS * 3 * 2];
			public float* m_offMeshConRads;// [MAX_OFFMESH_CONNECTIONS];
			public byte* m_offMeshConDirs;// [MAX_OFFMESH_CONNECTIONS];
			public byte* m_offMeshConAreas;// [MAX_OFFMESH_CONNECTIONS];
			public ushort* m_offMeshConFlags;// [MAX_OFFMESH_CONNECTIONS];
			public uint* m_offMeshConId;// [MAX_OFFMESH_CONNECTIONS];
			public int m_offMeshConCount;
			///@}

			/// @name Convex Volumes.
			///@{
			public static int MAX_VOLUMES = 256;
			public ConvexVolume* m_volumes;// [MAX_VOLUMES];
			public int m_volumeCount;
			///@}

			public InputGeom()
			//                  :
			//      m_chunkyMesh(0),
			//m_mesh(0),
			//m_hasBuildSettings(false),
			//m_offMeshConCount(0),
			//m_volumeCount(0)
			{
				m_meshBMin = rcNewArray<float>(3, rcAllocHint.RC_ALLOC_PERM);
				m_meshBMax = rcNewArray<float>(3, rcAllocHint.RC_ALLOC_PERM);
				m_offMeshConVerts = rcNewArray<float>(MAX_OFFMESH_CONNECTIONS * 3 * 2, rcAllocHint.RC_ALLOC_PERM);
				m_offMeshConRads = rcNewArray<float>(MAX_OFFMESH_CONNECTIONS, rcAllocHint.RC_ALLOC_PERM);
				m_offMeshConDirs = rcNewArray<byte>(MAX_OFFMESH_CONNECTIONS, rcAllocHint.RC_ALLOC_PERM);
				m_offMeshConAreas = rcNewArray<byte>(MAX_OFFMESH_CONNECTIONS, rcAllocHint.RC_ALLOC_PERM);
				m_offMeshConFlags = rcNewArray<ushort>(MAX_OFFMESH_CONNECTIONS, rcAllocHint.RC_ALLOC_PERM);
				m_offMeshConId = rcNewArray<uint>(MAX_OFFMESH_CONNECTIONS, rcAllocHint.RC_ALLOC_PERM);
				m_volumes = rcNewArray<ConvexVolume>(MAX_VOLUMES, rcAllocHint.RC_ALLOC_PERM);
			}

			~InputGeom()
			{
				rcDelete(m_chunkyMesh);
				rcDelete(m_mesh);
				rcDelete(m_meshBMin);
				rcDelete(m_meshBMax);
				rcDelete(m_offMeshConVerts);
				rcDelete(m_offMeshConRads);
				rcDelete(m_offMeshConDirs);
				rcDelete(m_offMeshConAreas);
				rcDelete(m_offMeshConFlags);
				rcDelete(m_offMeshConId);
				rcDelete(m_volumes);
			}

			/// Method to return static mesh data.
			public rcMeshLoaderObj* getMesh() { return m_mesh; }
			public float* getMeshBoundsMin() { return m_meshBMin; }
			public float* getMeshBoundsMax() { return m_meshBMax; }
			public float* getNavMeshBoundsMin() { return m_hasBuildSettings ? m_buildSettings->navMeshBMin : m_meshBMin; }
			public float* getNavMeshBoundsMax() { return m_hasBuildSettings ? m_buildSettings->navMeshBMax : m_meshBMax; }
			public rcChunkyTriMesh* getChunkyMesh() { return m_chunkyMesh; }
			public BuildSettings* getBuildSettings()
			{
				return m_hasBuildSettings ? m_buildSettings : (BuildSettings*)0;
			}
			//public bool raycastMesh(float* src, float* dst, ref float tmin);

			/// @name Off-Mesh connections.
			///@{
			public int getOffMeshConnectionCount() { return m_offMeshConCount; }
			public float* getOffMeshConnectionVerts() { return m_offMeshConVerts; }
			public float* getOffMeshConnectionRads() { return m_offMeshConRads; }
			public byte* getOffMeshConnectionDirs() { return m_offMeshConDirs; }
			public byte* getOffMeshConnectionAreas() { return m_offMeshConAreas; }
			public ushort* getOffMeshConnectionFlags() { return m_offMeshConFlags; }
			public uint* getOffMeshConnectionId() { return m_offMeshConId; }
			public void addOffMeshConnection(float* spos, float* epos, float rad,
				byte bidir, byte area, ushort flags)
			{
				if (m_offMeshConCount >= MAX_OFFMESH_CONNECTIONS) return;
				float* v = &m_offMeshConVerts[m_offMeshConCount * 3 * 2];
				m_offMeshConRads[m_offMeshConCount] = rad;
				m_offMeshConDirs[m_offMeshConCount] = bidir;
				m_offMeshConAreas[m_offMeshConCount] = area;
				m_offMeshConFlags[m_offMeshConCount] = flags;
				m_offMeshConId[m_offMeshConCount] = (uint)(1000 + m_offMeshConCount);
				rcVcopy(&v[0], spos);
				rcVcopy(&v[3], epos);
				m_offMeshConCount++;
			}
			public void deleteOffMeshConnection(int i)
			{
				m_offMeshConCount--;
				float* src = &m_offMeshConVerts[m_offMeshConCount * 3 * 2];
				float* dst = &m_offMeshConVerts[i * 3 * 2];
				rcVcopy(&dst[0], &src[0]);
				rcVcopy(&dst[3], &src[3]);
				m_offMeshConRads[i] = m_offMeshConRads[m_offMeshConCount];
				m_offMeshConDirs[i] = m_offMeshConDirs[m_offMeshConCount];
				m_offMeshConAreas[i] = m_offMeshConAreas[m_offMeshConCount];
				m_offMeshConFlags[i] = m_offMeshConFlags[m_offMeshConCount];
			}
			///@}

			/// @name Box Volumes.
			///@{
			public int getConvexVolumeCount() { return m_volumeCount; }
			public ConvexVolume* getConvexVolumes() { return m_volumes; }
			public void addConvexVolume(float* verts, int nverts,
				 float minh, float maxh, byte area)
			{
				if (m_volumeCount >= MAX_VOLUMES) return;
				ConvexVolume* vol = &m_volumes[m_volumeCount++];
				memset(vol, 0, sizeof(ConvexVolume));
				memcpy(vol->verts, verts, sizeof(float) * 3 * nverts);
				vol->hmin = minh;
				vol->hmax = maxh;
				vol->nverts = nverts;
				vol->area = area;
			}
			public void deleteConvexVolume(int i)
			{
				m_volumeCount--;
				m_volumes[i] = m_volumes[m_volumeCount];
			}

			///@}

			public bool loadMeshFile(rcContext* ctx, string filepath)
			{
				if (m_mesh != null)
				{
					rcDelete(m_chunkyMesh);
					m_chunkyMesh = (rcChunkyTriMesh*)0;
					rcDelete(m_mesh);
					m_mesh = null;
				}
				m_offMeshConCount = 0;
				m_volumeCount = 0;

				_mesh = new rcMeshLoaderObj();
				if (null == m_mesh)
				{
					//ctx->log(RC_LOG_ERROR, "loadMesh: Out of memory 'm_mesh'.");
					return false;
				}
				if (!m_mesh->load(filepath))
				{
					//ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath);
					return false;
				}

				rcCalcBounds(m_mesh->getVerts(), m_mesh->getVertCount(), m_meshBMin, m_meshBMax);

				m_chunkyMesh = (rcChunkyTriMesh*)dtAlloc(sizeof(rcChunkyTriMesh), dtAllocHint.DT_ALLOC_PERM);//new rcChunkyTriMesh;
				if (null == m_chunkyMesh)
				{
					//ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Out of memory 'm_chunkyMesh'.");
					return false;
				}
				if (!rcCreateChunkyTriMesh(m_mesh->getVerts(), m_mesh->getTris(), m_mesh->getTriCount(), 256, m_chunkyMesh))
				{
					//ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Failed to build chunky mesh.");
					return false;
				}

				return true;
			}


			public bool loadMeshData(rcContext* ctx, char* meshData, long meshDataLen)
			{
				if (null != m_mesh)
				{
					rcDelete(m_chunkyMesh);
					m_chunkyMesh = null;
					rcDelete(m_mesh);
					m_mesh = null;
				}
				m_offMeshConCount = 0;
				m_volumeCount = 0;

				m_mesh = rcNew<rcMeshLoaderObj>(rcAllocHint.RC_ALLOC_PERM); //new rcMeshLoaderObj;
				if (null == m_mesh)
				{
					//ctx->log(RC_LOG_ERROR, "loadMesh: Out of memory 'm_mesh'.");
					return false;
				}
				if (!m_mesh->loadMeshObj(meshData, meshDataLen))
				{
					//ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", text);
					return false;
				}

				rcCalcBounds(m_mesh->getVerts(), m_mesh->getVertCount(), m_meshBMin, m_meshBMax);

				m_chunkyMesh = (rcChunkyTriMesh*)dtAlloc(sizeof(rcChunkyTriMesh), dtAllocHint.DT_ALLOC_PERM);//new rcChunkyTriMesh;
				if (null == m_chunkyMesh)
				{
					//ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Out of memory 'm_chunkyMesh'.");
					return false;
				}
				if (!rcCreateChunkyTriMesh(m_mesh->getVerts(), m_mesh->getTris(), m_mesh->getTriCount(), 256, m_chunkyMesh))
				{
					//ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Failed to build chunky mesh.");
					return false;
				}

				return true;
			}

			public bool loadMesh(rcContext* ctx, float* m_verts, int* m_tris, int m_vertCount, int m_triCount)
			{
				if (null != m_mesh)
				{
					rcDelete(m_chunkyMesh);
					m_chunkyMesh = null;
					rcDelete(m_mesh);
					m_mesh = null;
				}
				m_offMeshConCount = 0;
				m_volumeCount = 0;

				m_mesh = rcNew<rcMeshLoaderObj>(rcAllocHint.RC_ALLOC_PERM);//new rcMeshLoaderObj;
				if (null == m_mesh)
				{
					//ctx->log(RC_LOG_ERROR, "loadMesh: Out of memory 'm_mesh'.");
					return false;
				}
				m_mesh->setMesh(m_verts, m_tris, m_vertCount, m_triCount);

				rcCalcBounds(m_mesh->getVerts(), m_mesh->getVertCount(), m_meshBMin, m_meshBMax);

				m_chunkyMesh = (rcChunkyTriMesh*)dtAlloc(sizeof(rcChunkyTriMesh), dtAllocHint.DT_ALLOC_PERM);//new rcChunkyTriMesh;
				if (null == m_chunkyMesh)
				{
					//ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Out of memory 'm_chunkyMesh'.");
					return false;
				}
				if (!rcCreateChunkyTriMesh(m_mesh->getVerts(), m_mesh->getTris(), m_mesh->getTriCount(), 256, m_chunkyMesh))
				{
					//ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Failed to build chunky mesh.");
					return false;
				}

				return true;
			}
		};

		public class BuildContext : rcContext
		{
			//public BuildContext() { }

			/// Dumps the log to stdout.
			//public void dumpLog(char* format, params object[] args) { }
			///// Returns number of log messages.
			//public int getLogCount() { return 0; }
			///// Returns log message text.
			//public char* getLogText(int i) { return null; }

			/// Virtual functions for custom implementations.
			///@{
			//public virtual void doResetLog() { }
			//public virtual void doLog(rcLogCategory category, char* msg, int len) { }
			//public virtual void doResetTimers() { }
			//public virtual void doStartTimer(rcTimerLabel label) { }
			//public virtual void doStopTimer(rcTimerLabel label) { }
			//public virtual int doGetAccumulatedTime(rcTimerLabel label) { return 0; }
			///@}
		};

		static void calcVel(float* vel, float* pos, float* tgt, float speed)
		{
			dtVsub(vel, tgt, pos);
			vel[1] = 0.0f;
			dtVnormalize(vel);
			dtVscale(vel, vel, speed);
		}

		static bool inRange(float* v1, float* v2, float r, float h)
		{
			float dx = v2[0] - v1[0];
			float dy = v2[1] - v1[1];
			float dz = v2[2] - v1[2];
			return (dx * dx + dz * dz) < r * r && fabsf(dy) < h;
		}

		// This function checks if the path has a small U-turn, that is,
		// a polygon further in the path is adjacent to the first polygon
		// in the path. If that happens, a shortcut is taken.
		// This can happen if the target (T) location is at tile boundary,
		// and we're (S) approaching it parallel to the tile edge.
		// The choice at the vertex can be arbitrary, 
		//  +---+---+
		//  |:::|:::|
		//  +-S-+-T-+
		//  |:::|   | <-- the step can end up in here, resulting U-turn path.
		//  +---+---+
		static int fixupShortcuts(dtPolyRef* path, int npath, dtNavMeshQuery* navQuery)
		{
			if (npath < 3)
				return npath;

			// Get connected polygons
			int maxNeis = 16;
			dtPolyRef* neis = stackalloc dtPolyRef[maxNeis];
			int nneis = 0;

			dtMeshTile* tile = (dtMeshTile*)0;
			dtPoly* poly = (dtPoly*)0;
			if (dtStatusFailed(navQuery->getAttachedNavMesh()->getTileAndPolyByRef(path[0], &tile, &poly)))
				return npath;

			for (uint k = poly->firstLink; k != DT_NULL_LINK; k = tile->links[k].next)
			{
				dtLink* link = &tile->links[k];
				if (link->Ref != 0)
				{
					if (nneis < maxNeis)
						neis[nneis++] = link->Ref;
				}
			}

			// If any of the neighbour polygons is within the next few polygons
			// in the path, short cut to that polygon directly.
			int maxLookAhead = 6;
			int cut = 0;
			for (int i = dtMin(maxLookAhead, npath) - 1; i > 1 && cut == 0; i--)
			{
				for (int j = 0; j < nneis; j++)
				{
					if (path[i] == neis[j])
					{
						cut = i;
						break;
					}
				}
			}
			if (cut > 1)
			{
				int offset = cut - 1;
				npath -= offset;
				for (int i = 1; i < npath; i++)
					path[i] = path[i + offset];
			}

			return npath;
		}

		static bool getSteerTarget(dtNavMeshQuery* navQuery, float* startPos, float* endPos,
			 float minTargetDist,
			 dtPolyRef* path, int pathSize,
			float* steerPos, ref byte steerPosFlag, ref dtPolyRef steerPosRef,
			float* outPoints = null, int* outPointCount = null)
		{
			// Find steer target.
			int MAX_STEER_POINTS = 3;
			float* steerPath = stackalloc float[MAX_STEER_POINTS * 3];
			byte* steerPathFlags = stackalloc byte[MAX_STEER_POINTS];
			dtPolyRef* steerPathPolys = stackalloc dtPolyRef[MAX_STEER_POINTS];
			int nsteerPath = 0;
			navQuery->findStraightPath(startPos, endPos, path, pathSize,
				steerPath, steerPathFlags, steerPathPolys, ref nsteerPath, MAX_STEER_POINTS);
			if (0 == nsteerPath)
				return false;

			if (outPoints != null && outPointCount != null)
			{
				*outPointCount = nsteerPath;
				for (int i = 0; i < nsteerPath; ++i)
					dtVcopy(&outPoints[i * 3], &steerPath[i * 3]);
			}


			// Find vertex far enough to steer to.
			int ns = 0;
			while (ns < nsteerPath)
			{
				// Stop at Off-Mesh link or when point is further than slop away.
				if ((steerPathFlags[ns] & (byte)dtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0 ||
					!inRange(&steerPath[ns * 3], startPos, minTargetDist, 1000.0f))
					break;
				ns++;
			}
			// Failed to find good point to steer to.
			if (ns >= nsteerPath)
				return false;

			dtVcopy(steerPos, &steerPath[ns * 3]);
			steerPos[1] = startPos[1];
			steerPosFlag = steerPathFlags[ns];
			steerPosRef = steerPathPolys[ns];

			return true;
		}

		public class Sample_SoloMesh
		{
			public InputGeom _geom;
			public dtNavMesh _navMesh;
			public dtNavMeshQuery _navQuery;
			public rcContext _ctx;

			//这4个会被gc移动
			public InputGeom* m_geom
			{
				get => _geom == null ? null : (InputGeom*)Unsafe.AsPointer(ref _geom);
				set => _geom = value != null ? *value : null;
			}
			private dtNavMesh* m_navMeshFree;
			public dtNavMesh* m_navMesh
			{
				get => _navMesh == null ? null : (dtNavMesh*)Unsafe.AsPointer(ref _navMesh);
				set => _navMesh = value != null ? *value : null;
			}
			private dtNavMeshQuery* m_navQueryFree;
			public dtNavMeshQuery* m_navQuery
			{
				get => _navQuery == null ? null : (dtNavMeshQuery*)Unsafe.AsPointer(ref _navQuery);
				set => _navQuery = value != null ? *value : null;
			}
			public rcContext* m_ctx
			{
				get => _ctx == null ? null : (rcContext*)Unsafe.AsPointer(ref _ctx);
				set => _ctx = value != null ? *value : null;
			}

			public float m_cellSize;
			public float m_cellHeight;
			public float m_agentHeight;
			public float m_agentRadius;
			public float m_agentMaxClimb;
			public float m_agentMaxSlope;
			public float m_regionMinSize;
			public float m_regionMergeSize;
			public float m_edgeMaxLen;
			public float m_edgeMaxError;
			public float m_vertsPerPoly;
			public float m_detailSampleDist;
			public float m_detailSampleMaxError;
			public int m_partitionType;

			public bool m_filterLowHangingObstacles;
			public bool m_filterLedgeSpans;
			public bool m_filterWalkableLowHeightSpans;

			public bool m_keepInterResults;
			public float m_totalBuildTimeMs;

			public byte* m_triareas;
			public rcHeightfield* m_solid;
			public rcCompactHeightfield* m_chf;
			public rcContourSet* m_cset;
			public rcPolyMesh* m_pmesh;
			public rcConfig m_cfg;
			public rcPolyMeshDetail* m_dmesh;

			public int m_straightPathOptions = 2;

			public static int MAX_POLYS = 256;
			public static int MAX_SMOOTH = 2048;

			public dtQueryFilter _filter;
			public dtQueryFilter* m_filter
			{
				get => _filter == null ? null : (dtQueryFilter*)Unsafe.AsPointer(ref _filter);
				set => _filter = value != null ? *value : null;
			}

			public dtStatus m_pathFindStatus;

			public dtPolyRef m_startRef;
			public dtPolyRef m_endRef;
			public dtPolyRef* m_polys;// [MAX_POLYS];
			public int* m_npolys;
			public byte* m_straightPathFlags;// [MAX_POLYS];
			public dtPolyRef* m_straightPathPolys;// [MAX_POLYS];
			public float* m_polyPickExt;// [3];

			public float getAgentRadius() { return m_agentRadius; }
			public float getAgentHeight() { return m_agentHeight; }
			public float getAgentClimb() { return m_agentMaxClimb; }

			public float* m_targetPos;// [3];
			public dtPolyRef m_targetRef;

			public const int AGENT_MAX_TRAIL = 64;
			public static int MAX_AGENTS = 128;

			public Sample_SoloMesh()
			//                  :
			//      m_geom(0),
			//m_navMesh(0),
			//m_navQuery(0),
			//m_filterLowHangingObstacles(true),
			//m_filterLedgeSpans(true),
			//m_filterWalkableLowHeightSpans(true),
			//m_ctx(0)
			{
				m_npolys = (int*)dtAlloc(sizeof(int), dtAllocHint.DT_ALLOC_PERM);

				m_polys = rcNewArray<dtPolyRef>(MAX_POLYS, rcAllocHint.RC_ALLOC_PERM);// [MAX_POLYS]; 
				m_straightPathFlags = rcNewArray<byte>(MAX_POLYS, rcAllocHint.RC_ALLOC_PERM);// [MAX_POLYS];
				m_straightPathPolys = rcNewArray<dtPolyRef>(MAX_POLYS, rcAllocHint.RC_ALLOC_PERM);// [MAX_POLYS];
				m_polyPickExt = rcNewArray<float>(3, rcAllocHint.RC_ALLOC_PERM);// [3];
				m_targetPos = rcNewArray<float>(3, rcAllocHint.RC_ALLOC_PERM);// [3];

				_filter = new dtQueryFilter();

				m_filterLowHangingObstacles = true;
				m_filterLedgeSpans = true;
				m_filterWalkableLowHeightSpans = true;

				_ctx = new BuildContext();
				resetCommonSettings();
				m_navQuery = m_navQueryFree = dtAllocNavMeshQuery();

				m_polyPickExt[0] = 8;
				m_polyPickExt[1] = 16;
				m_polyPickExt[2] = 8;
			}
			~Sample_SoloMesh()
			{
				dtFreeNavMeshQuery(m_navQuery);
				dtFreeNavMeshQuery(m_navQueryFree);
				rcFree(m_npolys);
				rcFree(m_polys);
				rcFree(m_straightPathFlags);
				rcFree(m_straightPathPolys);
				rcFree(m_polyPickExt);
				rcFree(m_targetPos);
				cleanup();
			}

			public InputGeom* getInputGeom() { return m_geom; }
			public dtNavMesh* getNavMesh() { return m_navMesh; }
			public dtNavMeshQuery* getNavMeshQuery() { return m_navQuery; }
			public rcContext* getCtx() { return m_ctx; }

			public void setInputGeom(InputGeom geom)
			{
				_geom = geom;
			}

			public void setBuildSettings(InputGeom* geom)
			{
				m_geom = geom;

				BuildSettings* buildSettings = geom->getBuildSettings();
				if (buildSettings != null)
				{
					m_cellSize = buildSettings->cellSize;
					m_cellHeight = buildSettings->cellHeight;
					m_agentHeight = buildSettings->agentHeight;
					m_agentRadius = buildSettings->agentRadius;
					m_agentMaxClimb = buildSettings->agentMaxClimb;
					m_agentMaxSlope = buildSettings->agentMaxSlope;
					m_regionMinSize = buildSettings->regionMinSize;
					m_regionMergeSize = buildSettings->regionMergeSize;
					m_edgeMaxLen = buildSettings->edgeMaxLen;
					m_edgeMaxError = buildSettings->edgeMaxError;
					m_vertsPerPoly = buildSettings->vertsPerPoly;
					m_detailSampleDist = buildSettings->detailSampleDist;
					m_detailSampleMaxError = buildSettings->detailSampleMaxError;
					m_partitionType = buildSettings->partitionType;
				}
			}

			public void setBuildSettings(BuildSettings buildSettings)
			{
				m_cellSize = buildSettings.cellSize;
				m_cellHeight = buildSettings.cellHeight;
				m_agentHeight = buildSettings.agentHeight;
				m_agentRadius = buildSettings.agentRadius;
				m_agentMaxClimb = buildSettings.agentMaxClimb;
				m_agentMaxSlope = buildSettings.agentMaxSlope;
				m_regionMinSize = buildSettings.regionMinSize;
				m_regionMergeSize = buildSettings.regionMergeSize;
				m_edgeMaxLen = buildSettings.edgeMaxLen;
				m_edgeMaxError = buildSettings.edgeMaxError;
				m_vertsPerPoly = buildSettings.vertsPerPoly;
				m_detailSampleDist = buildSettings.detailSampleDist;
				m_detailSampleMaxError = buildSettings.detailSampleMaxError;
				m_partitionType = buildSettings.partitionType;
			}

			public void collectSettings(ref BuildSettings settings)
			{
				settings.cellSize = m_cellSize;
				settings.cellHeight = m_cellHeight;
				settings.agentHeight = m_agentHeight;
				settings.agentRadius = m_agentRadius;
				settings.agentMaxClimb = m_agentMaxClimb;
				settings.agentMaxSlope = m_agentMaxSlope;
				settings.regionMinSize = m_regionMinSize;
				settings.regionMergeSize = m_regionMergeSize;
				settings.edgeMaxLen = m_edgeMaxLen;
				settings.edgeMaxError = m_edgeMaxError;
				settings.vertsPerPoly = m_vertsPerPoly;
				settings.detailSampleDist = m_detailSampleDist;
				settings.detailSampleMaxError = m_detailSampleMaxError;
				settings.partitionType = m_partitionType;
			}

			public void resetCommonSettings()
			{
				m_cellSize = 0.3f;
				m_cellHeight = 0.2f;
				m_agentHeight = 2.0f;
				m_agentRadius = 0.6f;
				m_agentMaxClimb = 0.9f;
				m_agentMaxSlope = 45.0f;
				m_regionMinSize = 8;
				m_regionMergeSize = 20;
				m_edgeMaxLen = 12.0f;
				m_edgeMaxError = 1.3f;
				m_vertsPerPoly = 6.0f;
				m_detailSampleDist = 6.0f;
				m_detailSampleMaxError = 1.0f;
				m_partitionType = (int)SamplePartitionType.SAMPLE_PARTITION_WATERSHED;
			}

			public void cleanup()
			{
				rcDelete(m_triareas);
				m_triareas = null;
				rcFreeHeightField(m_solid);
				m_solid = null;
				rcFreeCompactHeightfield(m_chf);
				m_chf = null;
				rcFreeContourSet(m_cset);
				m_cset = null;
				rcFreePolyMesh(m_pmesh);
				m_pmesh = null;
				rcFreePolyMeshDetail(m_dmesh);
				m_dmesh = null;
				dtFreeNavMesh(m_navMesh);
				m_navMesh = null;
				rcFree(m_navMeshFree);
				m_navMeshFree = null;
			}

			public bool loadNavmesh(string path)
			{
				if (!File.Exists(path))
					return false;
				var navMeshData = File.ReadAllBytes(path);
				return loadNavmesh(navMeshData);
			}

			public bool loadNavmesh(byte[] navMeshData)
			{
				cleanup();

				m_navMesh = m_navMeshFree = loadAll(navMeshData);
				m_navQuery = m_navQueryFree = dtAllocNavMeshQuery();
				m_navQuery->init(m_navMesh, 2048);

				return m_navMesh != null;
			}

			public dtNavMesh* loadAll(byte[] navmeshData)
			{
				byte* navMeshData = (byte*)dtAlloc(navmeshData.Length, dtAllocHint.DT_ALLOC_PERM);
				byte* bufSize = navMeshData + navmeshData.Length;
				fixed (byte* ptr = &navmeshData[0])
					memcpy(navMeshData, ptr, navmeshData.Length);

				// Read header
				NavMeshSetHeader header = new NavMeshSetHeader();
				memcpy(&header, navMeshData, sizeof(NavMeshSetHeader));
				navMeshData += sizeof(NavMeshSetHeader);

				if (header.magic != NAVMESHSET_MAGIC)
				{
					dtFree(navMeshData);
					return null;
				}
				if (header.version != NAVMESHSET_VERSION)
				{
					dtFree(navMeshData);
					return null;
				}

				var mesh = m_navMeshFree = dtAllocNavMesh();
				if (null == mesh)
				{
					dtFree(navMeshData);
					return null;
				}
				dtStatus status = mesh->init(&header.@params);
				if (dtStatusFailed(status))
				{
					dtFree(navMeshData);
					return null;
				}

				// Read tiles.
				for (int i = 0; i < header.numTiles; ++i)
				{
					NavMeshTileHeader tileHeader = new NavMeshTileHeader();
					memcpy(&tileHeader, navMeshData, sizeof(NavMeshTileHeader));
					navMeshData += sizeof(NavMeshTileHeader);

					if (0 == tileHeader.tileRef || 0 == tileHeader.dataSize)
						break;

					if ((bufSize - (navMeshData + tileHeader.dataSize)) < 0)
					{
						dtFree(navMeshData);
						return null;
					}
					mesh->addTile(navMeshData, tileHeader.dataSize, (int)dtTileFlags.DT_TILE_FREE_DATA, tileHeader.tileRef, (uint*)0);
				}
				return mesh;
			}

			public void saveAll(string path)
			{
				var mesh = m_navMesh;
				if (null == mesh)
					return;

				using (var fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite))
				{
					// Store header.
					NavMeshSetHeader header = new NavMeshSetHeader();
					header.magic = NAVMESHSET_MAGIC;
					header.version = NAVMESHSET_VERSION;
					header.numTiles = 0;
					for (int i = 0; i < mesh->getMaxTiles(); ++i)
					{
						dtMeshTile* tile = mesh->getTile(i);
						if (null == tile || null == tile->header || 0 == tile->dataSize) continue;
						header.numTiles++;
					}
					memcpy(&header.@params, mesh->getParams(), sizeof(dtNavMeshParams));
					byte* ptr = (byte*)&header;
					for (int n = 0; n < sizeof(NavMeshSetHeader); n++)
						fs.WriteByte(ptr[n]);

					// Store tiles.
					for (int i = 0; i < mesh->getMaxTiles(); ++i)
					{
						dtMeshTile* tile = mesh->getTile(i);
						if (null == tile || null == tile->header || 0 == tile->dataSize) continue;

						NavMeshTileHeader tileHeader = new NavMeshTileHeader();
						tileHeader.tileRef = mesh->getTileRef(tile);
						tileHeader.dataSize = tile->dataSize;
						ptr = (byte*)&tileHeader;
						for (int n = 0; n < sizeof(NavMeshTileHeader); n++)
							fs.WriteByte(ptr[n]);

						ptr = tile->data;
						for (int n = 0; n < tile->dataSize; n++)
							fs.WriteByte(ptr[n]);
					}
				}
			}

			public bool Build()
			{
				if (null == m_geom || null == m_geom->getMesh())
				{
					return false;
				}

				cleanup();

				float* bmin = m_geom->getNavMeshBoundsMin();
				float* bmax = m_geom->getNavMeshBoundsMax();
				float* verts = m_geom->getMesh()->getVerts();
				int nverts = m_geom->getMesh()->getVertCount();
				int* tris = m_geom->getMesh()->getTris();
				int ntris = m_geom->getMesh()->getTriCount();

				//
				// Step 1. Initialize build config.
				//

				// Init build configuration from GUI
				var cfg = new rcConfig();
				var m_cfg = &cfg; // rcNew<rcConfig>(rcAllocHint.RC_ALLOC_PERM);
								  //memset(m_cfg, 0, sizeof(rcConfig));
				m_cfg->cs = m_cellSize;
				m_cfg->ch = m_cellHeight;
				m_cfg->walkableSlopeAngle = m_agentMaxSlope;
				m_cfg->walkableHeight = (int)ceilf(m_agentHeight / m_cfg->ch);
				m_cfg->walkableClimb = (int)floorf(m_agentMaxClimb / m_cfg->ch);
				m_cfg->walkableRadius = (int)ceilf(m_agentRadius / m_cfg->cs);
				m_cfg->maxEdgeLen = (int)(m_edgeMaxLen / m_cellSize);
				m_cfg->maxSimplificationError = m_edgeMaxError;
				m_cfg->minRegionArea = (int)rcSqr(m_regionMinSize);      // Note: area = size*size
				m_cfg->mergeRegionArea = (int)rcSqr(m_regionMergeSize);  // Note: area = size*size
				m_cfg->maxVertsPerPoly = (int)m_vertsPerPoly;
				m_cfg->detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist;
				m_cfg->detailSampleMaxError = m_cellHeight * m_detailSampleMaxError;

				// Set the area where the navigation will be build.
				// Here the bounds of the input mesh are used, but the
				// area could be specified by an user defined box, etc.
				rcVcopy(m_cfg->bmin, bmin);
				rcVcopy(m_cfg->bmax, bmax);
				rcCalcGridSize(m_cfg->bmin, m_cfg->bmax, m_cfg->cs, ref m_cfg->width, ref m_cfg->height);

				//
				// Step 2. Rasterize input polygon soup.
				//

				// Allocate voxel heightfield where we rasterize our input data to.
				m_solid = rcAllocHeightfield();
				if (null == m_solid)
				{
					return false;
				}

				if (!rcCreateHeightfield(m_ctx, ref *m_solid, m_cfg->width, m_cfg->height, m_cfg->bmin, m_cfg->bmax, m_cfg->cs, m_cfg->ch))
				{
					//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create solid heightfield.");
					return false;
				}

				// Allocate array that can hold triangle area types.
				// If you have multiple meshes you need to process, allocate
				// and array which can hold the max number of triangles you need to process.
				m_triareas = rcNewArray<byte>(ntris, rcAllocHint.RC_ALLOC_PERM);//new byte[ntris];
				if (null == m_triareas)
				{
					//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'm_triareas' (%d).", ntris);
					return false;
				}

				// Find triangles which are walkable based on their slope and rasterize them.
				// If your input data is multiple meshes, you can transform them here, calculate
				// the are type for each of the meshes and rasterize them.
				memset(m_triareas, 0, ntris * sizeof(byte));
				rcMarkWalkableTriangles(m_ctx, m_cfg->walkableSlopeAngle, verts, nverts, tris, ntris, m_triareas);
				if (!rcRasterizeTriangles(m_ctx, verts, nverts, tris, m_triareas, ntris, ref *m_solid, m_cfg->walkableClimb))
				{
					//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Could not rasterize triangles.");
					return false;
				}

				if (!m_keepInterResults)
				{
					rcDelete(m_triareas);
					m_triareas = (byte*)0;
				}

				//
				// Step 3. Filter walkable surfaces.
				//

				// Once all geometry is rasterized, we do initial pass of filtering to
				// remove unwanted overhangs caused by the conservative rasterization
				// as well as filter spans where the character cannot possibly stand.
				if (m_filterLowHangingObstacles)
					rcFilterLowHangingWalkableObstacles(m_ctx, m_cfg->walkableClimb, ref *m_solid);
				if (m_filterLedgeSpans)
					rcFilterLedgeSpans(m_ctx, m_cfg->walkableHeight, m_cfg->walkableClimb, ref *m_solid);
				if (m_filterWalkableLowHeightSpans)
					rcFilterWalkableLowHeightSpans(m_ctx, m_cfg->walkableHeight, ref *m_solid);


				//
				// Step 4. Partition walkable surface to simple regions.
				//

				// Compact the heightfield so that it is faster to handle from now on.
				// This will result more cache coherent data as well as the neighbours
				// between walkable cells will be calculated.
				m_chf = rcAllocCompactHeightfield();
				if (null == m_chf)
				{
					//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'.");
					return false;
				}
				if (!rcBuildCompactHeightfield(m_ctx, m_cfg->walkableHeight, m_cfg->walkableClimb, ref *m_solid, ref *m_chf))
				{
					//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data.");
					return false;
				}

				if (!m_keepInterResults)
				{
					rcFreeHeightField(m_solid);
					m_solid = null;
				}

				// Erode the walkable area by agent radius.
				if (!rcErodeWalkableArea(m_ctx, m_cfg->walkableRadius, ref *m_chf))
				{
					//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode.");
					return false;
				}

				// (Optional) Mark areas.
				ConvexVolume* vols = m_geom->getConvexVolumes();
				for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i)
					rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (byte)vols[i].area, ref *m_chf);


				// Partition the heightfield so that we can use simple algorithm later to triangulate the walkable areas.
				// There are 3 partitioning methods, each with some pros and cons:
				// 1) Watershed partitioning
				//   - the classic Recast partitioning
				//   - creates the nicest tessellation
				//   - usually slowest
				//   - partitions the heightfield into nice regions without holes or overlaps
				//   - the are some corner cases where this method creates produces holes and overlaps
				//      - holes may appear when a small obstacles is close to large open area (triangulation can handle this)
				//      - overlaps may occur if you have narrow spiral corridors (i.e stairs), this make triangulation to fail
				//   * generally the best choice if you precompute the navmesh, use this if you have large open areas
				// 2) Monotone partitioning
				//   - fastest
				//   - partitions the heightfield into regions without holes and overlaps (guaranteed)
				//   - creates long thin polygons, which sometimes causes paths with detours
				//   * use this if you want fast navmesh generation
				// 3) Layer partitoining
				//   - quite fast
				//   - partitions the heighfield into non-overlapping regions
				//   - relies on the triangulation code to cope with holes (thus slower than monotone partitioning)
				//   - produces better triangles than monotone partitioning
				//   - does not have the corner cases of watershed partitioning
				//   - can be slow and create a bit ugly tessellation (still better than monotone)
				//     if you have large open areas with small obstacles (not a problem if you use tiles)
				//   * good choice to use for tiled navmesh with medium and small sized tiles

				if (m_partitionType == (int)SamplePartitionType.SAMPLE_PARTITION_WATERSHED)
				{
					// Prepare for region partitioning, by calculating distance field along the walkable surface.
					if (!rcBuildDistanceField(m_ctx, ref *m_chf))
					{
						//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build distance field.");
						return false;
					}

					// Partition the walkable surface into simple regions without holes.
					if (!rcBuildRegions(m_ctx, ref *m_chf, 0, m_cfg->minRegionArea, m_cfg->mergeRegionArea))
					{
						//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build watershed regions.");
						return false;
					}
				}
				else if (m_partitionType == (int)SamplePartitionType.SAMPLE_PARTITION_MONOTONE)
				{
					// Partition the walkable surface into simple regions without holes.
					// Monotone partitioning does not need distancefield.
					if (!rcBuildRegionsMonotone(m_ctx, ref *m_chf, 0, m_cfg->minRegionArea, m_cfg->mergeRegionArea))
					{
						//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build monotone regions.");
						return false;
					}
				}
				else // SAMPLE_PARTITION_LAYERS
				{
					// Partition the walkable surface into simple regions without holes.
					if (!rcBuildLayerRegions(m_ctx, ref *m_chf, 0, m_cfg->minRegionArea))
					{
						//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build layer regions.");
						return false;
					}
				}

				//
				// Step 5. Trace and simplify region contours.
				//

				// Create contours.
				m_cset = rcAllocContourSet();
				if (null == m_cset)
				{
					//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'.");
					return false;
				}
				if (!rcBuildContours(m_ctx, ref *m_chf, m_cfg->maxSimplificationError, m_cfg->maxEdgeLen, ref *m_cset))
				{
					//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create contours.");
					return false;
				}

				//
				// Step 6. Build polygons mesh from contours.
				//

				// Build polygon navmesh from the contours.
				m_pmesh = rcAllocPolyMesh();
				if (null == m_pmesh)
				{
					//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'.");
					return false;
				}
				if (!rcBuildPolyMesh(m_ctx, ref *m_cset, m_cfg->maxVertsPerPoly, ref *m_pmesh))
				{
					//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours.");
					return false;
				}

				//
				// Step 7. Create detail mesh which allows to access approximate height on each polygon.
				//

				m_dmesh = rcAllocPolyMeshDetail();
				if (null == m_dmesh)
				{
					//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmdtl'.");
					return false;
				}

				if (!rcBuildPolyMeshDetail(m_ctx, ref *m_pmesh, ref *m_chf, m_cfg->detailSampleDist, m_cfg->detailSampleMaxError, ref *m_dmesh))
				{
					//m_//ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build detail mesh.");
					return false;
				}

				if (!m_keepInterResults)
				{
					rcFreeCompactHeightfield(m_chf);
					m_chf = (rcCompactHeightfield*)0;
					rcFreeContourSet(m_cset);
					m_cset = (rcContourSet*)0;
				}

				// At this point the navigation mesh data is ready, you can access it from m_pmesh.
				// See duDebugDrawPolyMesh or dtCreateNavMeshData as examples how to access the data.

				//
				// (Optional) Step 8. Create Detour data from Recast poly mesh.
				//

				// The GUI may allow more max points per polygon than Detour can handle.
				// Only build the detour navmesh if we do not exceed the limit.
				if (m_cfg->maxVertsPerPoly <= DT_VERTS_PER_POLYGON)
				{
					byte* navData = (byte*)0;
					int navDataSize = 0;

					// Update poly flags from areas.
					for (int i = 0; i < m_pmesh->npolys; ++i)
					{
						if (m_pmesh->areas[i] == RC_WALKABLE_AREA)
							m_pmesh->areas[i] = (byte)SamplePolyAreas.SAMPLE_POLYAREA_GROUND;

						if (m_pmesh->areas[i] == (byte)SamplePolyAreas.SAMPLE_POLYAREA_GROUND ||
							m_pmesh->areas[i] == (byte)SamplePolyAreas.SAMPLE_POLYAREA_GRASS ||
							m_pmesh->areas[i] == (byte)SamplePolyAreas.SAMPLE_POLYAREA_ROAD)
						{
							m_pmesh->flags[i] = (ushort)SamplePolyFlags.SAMPLE_POLYFLAGS_WALK;
						}
						else if (m_pmesh->areas[i] == (byte)SamplePolyAreas.SAMPLE_POLYAREA_WATER)
						{
							m_pmesh->flags[i] = (ushort)SamplePolyFlags.SAMPLE_POLYFLAGS_SWIM;
						}
						else if (m_pmesh->areas[i] == (byte)SamplePolyAreas.SAMPLE_POLYAREA_DOOR)
						{
							m_pmesh->flags[i] = (ushort)SamplePolyFlags.SAMPLE_POLYFLAGS_WALK | (ushort)SamplePolyFlags.SAMPLE_POLYFLAGS_DOOR;
						}
					}


					dtNavMeshCreateParams @params = new dtNavMeshCreateParams();
					//memset(&@params, 0, sizeof(dtNavMeshCreateParams));
					@params.verts = m_pmesh->verts;
					@params.vertCount = m_pmesh->nverts;
					@params.polys = m_pmesh->polys;
					@params.polyAreas = m_pmesh->areas;
					@params.polyFlags = m_pmesh->flags;
					@params.polyCount = m_pmesh->npolys;
					@params.nvp = m_pmesh->nvp;
					@params.detailMeshes = m_dmesh->meshes;
					@params.detailVerts = m_dmesh->verts;
					@params.detailVertsCount = m_dmesh->nverts;
					@params.detailTris = m_dmesh->tris;
					@params.detailTriCount = m_dmesh->ntris;
					@params.offMeshConVerts = m_geom->getOffMeshConnectionVerts();
					@params.offMeshConRad = m_geom->getOffMeshConnectionRads();
					@params.offMeshConDir = m_geom->getOffMeshConnectionDirs();
					@params.offMeshConAreas = m_geom->getOffMeshConnectionAreas();
					@params.offMeshConFlags = m_geom->getOffMeshConnectionFlags();
					@params.offMeshConUserID = m_geom->getOffMeshConnectionId();
					@params.offMeshConCount = m_geom->getOffMeshConnectionCount();
					@params.walkableHeight = m_agentHeight;
					@params.walkableRadius = m_agentRadius;
					@params.walkableClimb = m_agentMaxClimb;
					rcVcopy(@params.bmin, m_pmesh->bmin);
					rcVcopy(@params.bmax, m_pmesh->bmax);
					@params.cs = m_cfg->cs;
					@params.ch = m_cfg->ch;
					@params.buildBvTree = true;

					if (!dtCreateNavMeshData(&@params, &navData, &navDataSize))
					{
						//m_//ctx->log(RC_LOG_ERROR, "Could not build Detour navmesh.");
						return false;
					}

					m_navMesh = m_navMeshFree = dtAllocNavMesh();
					if (null == m_navMesh)
					{
						dtFree(navData);
						//m_//ctx->log(RC_LOG_ERROR, "Could not create Detour navmesh");
						return false;
					}

					dtStatus status;

					status = m_navMesh->init(navData, navDataSize, (int)dtTileFlags.DT_TILE_FREE_DATA);
					if (dtStatusFailed(status))
					{
						dtFree(navData);
						//m_//ctx->log(RC_LOG_ERROR, "Could not init Detour navmesh");
						return false;
					}

					status = m_navQuery->init(m_navMesh, 2048);
					if (dtStatusFailed(status))
					{
						//m_//ctx->log(RC_LOG_ERROR, "Could not init Detour navmesh query");
						return false;
					}
				}

				//ctx->stopTimer(rcTimerLabel.RC_TIMER_TOTAL);

				return true;
			}

			public void findPathStraight(float* m_spos, float* m_epos, float* m_straightPath, ref int m_nstraightPath, dtStraightPathOptions m_straightPathOptions)
			{
				if (null == m_navMesh)
					return;

				m_navQuery->findNearestPoly(m_spos, m_polyPickExt, m_filter, ref m_startRef, (float*)0);
				m_navQuery->findNearestPoly(m_epos, m_polyPickExt, m_filter, ref m_endRef, (float*)0);

				m_pathFindStatus = DT_FAILURE;

				m_navQuery->findPath(m_startRef, m_endRef, m_spos, m_epos, m_filter, m_polys, m_npolys, MAX_POLYS);
				m_nstraightPath = 0;
				if (*m_npolys != 0)
				{
					// In case of partial path, make sure the end point is clamped to the last polygon.
					float* epos = stackalloc float[3];
					dtVcopy(epos, m_epos);
					if (m_polys[*m_npolys - 1] != m_endRef)
						m_navQuery->closestPointOnPoly(m_polys[*m_npolys - 1], m_epos, epos, (bool*)0);

					m_navQuery->findStraightPath(m_spos, epos, m_polys, *m_npolys,
						m_straightPath, m_straightPathFlags,
						m_straightPathPolys, ref m_nstraightPath, MAX_POLYS, (int)m_straightPathOptions);
				}
			}

			public void findPathFollow(float* m_spos, float* m_epos, float* m_smoothPath, ref int m_nsmoothPath, dtStraightPathOptions m_straightPathOptions)
			{
				if (null == m_navMesh)
					return;

				m_navQuery->findNearestPoly(m_spos, m_polyPickExt, m_filter, ref m_startRef, (float*)0);
				m_navQuery->findNearestPoly(m_epos, m_polyPickExt, m_filter, ref m_endRef, (float*)0);
				m_pathFindStatus = DT_FAILURE;
				m_navQuery->findPath(m_startRef, m_endRef, m_spos, m_epos, m_filter, m_polys, m_npolys, MAX_POLYS);
				m_nsmoothPath = 0;

				if (*m_npolys != 0)
				{
					// Iterate over the path to find smooth path on the detail mesh surface.
					dtPolyRef* polys = stackalloc dtPolyRef[MAX_POLYS];
					memcpy(polys, m_polys, sizeof(dtPolyRef) * *m_npolys);
					int npolys = *m_npolys;

					float* iterPos = stackalloc float[3], targetPos = stackalloc float[3];
					m_navQuery->closestPointOnPoly(m_startRef, m_spos, iterPos, (bool*)0);
					m_navQuery->closestPointOnPoly(polys[npolys - 1], m_epos, targetPos, (bool*)0);

					float STEP_SIZE = 0.5f;
					float SLOP = 0.01f;

					m_nsmoothPath = 0;

					dtVcopy(&m_smoothPath[m_nsmoothPath * 3], iterPos);
					m_nsmoothPath++;

					float* steerPos = stackalloc float[3];
					float* delta = stackalloc float[3];
					float* moveTgt = stackalloc float[3];
					float* result = stackalloc float[3];
					dtPolyRef* visited = stackalloc dtPolyRef[16];
					float* startPos = stackalloc float[3], endPos = stackalloc float[3];

					// Move towards target a small advancement at a time until target reached or
					// when ran out of memory to store the path.
					while (npolys != 0 && m_nsmoothPath < MAX_SMOOTH)
					{
						// Find location to steer towards.
						//float* steerPos = stackalloc float[3];
						memset(steerPos, 0, sizeof(float) * 3);
						byte steerPosFlag = 0;
						dtPolyRef steerPosRef = 0;

						if (!getSteerTarget(m_navQuery, iterPos, targetPos, SLOP,
							polys, npolys, steerPos, ref steerPosFlag, ref steerPosRef))
							break;

						bool endOfPath = (steerPosFlag & (byte)dtStraightPathFlags.DT_STRAIGHTPATH_END) != 0;
						bool offMeshConnection = (steerPosFlag & (byte)dtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0;

						// Find movement delta.
						//float* delta = stackalloc float[3];
						memset(delta, 0, sizeof(float) * 3);
						float len = 0;
						dtVsub(delta, steerPos, iterPos);
						len = dtMathSqrtf(dtVdot(delta, delta));
						// If the steer target is end of path or off-mesh link, do not move past the location.
						if ((endOfPath || offMeshConnection) && len < STEP_SIZE)
							len = 1;
						else
							len = STEP_SIZE / len;
						//float* moveTgt = stackalloc float[3];
						memset(moveTgt, 0, sizeof(float) * 3);
						dtVmad(moveTgt, iterPos, delta, len);

						// Move
						//float* result = stackalloc float[3];
						//dtPolyRef* visited = stackalloc dtPolyRef[16];
						memset(result, 0, sizeof(float) * 3);
						memset(visited, 0, sizeof(dtPolyRef) * 16);

						int nvisited = 0;
						m_navQuery->moveAlongSurface(polys[0], iterPos, moveTgt, m_filter,
							result, visited, &nvisited, 16);

						npolys = dtMergeCorridorStartMoved(polys, npolys, MAX_POLYS, visited, nvisited);
						npolys = fixupShortcuts(polys, npolys, m_navQuery);

						float h = 0;
						m_navQuery->getPolyHeight(polys[0], result, &h);
						result[1] = h;
						dtVcopy(iterPos, result);

						// Handle end of path and off-mesh links when close enough.
						if (endOfPath && inRange(iterPos, steerPos, SLOP, 1.0f))
						{
							// Reached end of path.
							dtVcopy(iterPos, targetPos);
							if (m_nsmoothPath < MAX_SMOOTH)
							{
								dtVcopy(&m_smoothPath[m_nsmoothPath * 3], iterPos);
								m_nsmoothPath++;
							}
							break;
						}
						else if (offMeshConnection && inRange(iterPos, steerPos, SLOP, 1.0f))
						{
							// Reached off-mesh connection.
							//float* startPos = stackalloc float[3], endPos = stackalloc float[3];
							memset(startPos, 0, sizeof(float) * 3);
							memset(endPos, 0, sizeof(float) * 3);
							// Advance the path up to and over the off-mesh connection.
							dtPolyRef prevRef = 0, polyRef = polys[0];
							int npos = 0;
							while (npos < npolys && polyRef != steerPosRef)
							{
								prevRef = polyRef;
								polyRef = polys[npos];
								npos++;
							}
							for (int i = npos; i < npolys; ++i)
								polys[i - npos] = polys[i];
							npolys -= npos;

							// Handle the connection.
							dtStatus status = m_navMesh->getOffMeshConnectionPolyEndPoints(prevRef, polyRef, startPos, endPos);
							if (dtStatusSucceed(status))
							{
								if (m_nsmoothPath < MAX_SMOOTH)
								{
									dtVcopy(&m_smoothPath[m_nsmoothPath * 3], startPos);
									(m_nsmoothPath)++;
									// Hack to make the dotted path not visible during off-mesh connection.
									if (0 != (m_nsmoothPath & 1))
									{
										dtVcopy(&m_smoothPath[m_nsmoothPath * 3], startPos);
										(m_nsmoothPath)++;
									}
								}
								// Move position at the other side of the off-mesh link.
								dtVcopy(iterPos, endPos);
								float eh = 0.0f;
								m_navQuery->getPolyHeight(polys[0], iterPos, &eh);
								iterPos[1] = eh;
							}
						}

						// Store results.
						if (m_nsmoothPath < MAX_SMOOTH)
						{
							dtVcopy(&m_smoothPath[m_nsmoothPath * 3], iterPos);
							m_nsmoothPath++;
						}
					}
				}
			}

			public void getDrawNavMesh(float* vertsArray, ref int count)
			{
				ref dtNavMesh mesh = ref *getNavMesh();
				int idx = 0;
				for (int i = 0; i < mesh.getMaxTiles(); ++i)
				{
					dtMeshTile* tile = mesh.getTile(i);
					if (null == tile->header) continue;
					drawMeshTile(tile, vertsArray, ref idx);
				}
				count = idx;
			}

			void drawMeshTile(dtMeshTile* tile, float* vertsArray, ref int idx)
			{
				for (int i = 0; i < tile->header->polyCount; ++i)
				{
					dtPoly* p = &tile->polys[i];
					if (p->getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) // Skip off-mesh links.
						continue;

					dtPolyDetail* pd = &tile->detailMeshes[i];

					for (int j = 0; j < pd->triCount; ++j)
					{
						byte* t = &tile->detailTris[(pd->triBase + j) * 4];
						for (int k = 0; k < 3; ++k)
						{
							if (t[k] < p->vertCount)
							{
								float* pos = &tile->verts[p->verts[t[k]] * 3];
								vertsArray[idx] = pos[0];
								vertsArray[idx + 1] = pos[1];
								vertsArray[idx + 2] = pos[2];
								idx += 3;
							}
							else
							{
								float* pos = &tile->detailVerts[(pd->vertBase + t[k] - p->vertCount) * 3];
								vertsArray[idx] = pos[0];
								vertsArray[idx + 1] = pos[1];
								vertsArray[idx + 2] = pos[2];
								idx += 3;
							}
						}
					}
				}
			}

			public int getDrawNavMeshCount()
			{
				ref dtNavMesh mesh = ref *getNavMesh();
				int idx = 0;
				for (int i = 0; i < mesh.getMaxTiles(); ++i)
				{
					dtMeshTile* tile = mesh.getTile(i);
					if (null == tile->header) continue;
					drawMeshTile(tile, ref idx);
				}
				return idx;
			}

			void drawMeshTile(dtMeshTile* tile, ref int idx)
			{
				for (int i = 0; i < tile->header->polyCount; ++i)
				{
					dtPoly* p = &tile->polys[i];
					if (p->getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) // Skip off-mesh links.
						continue;

					dtPolyDetail* pd = &tile->detailMeshes[i];

					idx += pd->triCount * 9;
				}
			}
		};

		/*创建寻路网格实例*/
		public static Sample_SoloMesh CreateSoloMesh()
		{
			var sample = new Sample_SoloMesh();
			var geom = new InputGeom();
			sample.setInputGeom(geom);
			return sample;
		}

		/*设置构建寻路网格参数*/
		public static void SetBuildSettings(Sample_SoloMesh sample, BuildSettings settings)
		{
			if (null == sample)
				return;
			sample.setBuildSettings(settings);
		}

		/*加载网格模型.obj*/
		public static bool LoadMeshFile(Sample_SoloMesh sample, string path)
		{
			if (null == sample)
				return false;
			InputGeom* geom = sample.getInputGeom();
			rcContext* ctx = sample.getCtx();
			if (null == geom || null == ctx)
				return false;
			return geom->loadMeshFile(ctx, path);
		}

		/*加载网格模型.obj*/
		public static bool LoadMeshData(Sample_SoloMesh sample, string meshData)
		{
			if (null == sample)
				return false;
			InputGeom* geom = sample.getInputGeom();
			rcContext* ctx = sample.getCtx();
			if (null == geom || null == ctx)
				return false;
			char* buf = (char*)Unsafe.AsPointer(ref meshData.ToCharArray()[0]);
			return geom->loadMeshData(ctx, buf, meshData.Length);
		}

		/*加载网格数据 unity*/
		public static bool LoadMesh(Sample_SoloMesh sample, float* m_verts, int* m_tris, int m_vertCount, int m_triCount)
		{
			if (null == sample)
				return false;
			InputGeom* geom = sample.getInputGeom();
			rcContext* ctx = sample.getCtx();
			if (null == geom || null == ctx)
				return false;
			return geom->loadMesh(ctx, m_verts, m_tris, m_vertCount, m_triCount);
		}

		/*加载已经烘焙好的网格文件.bin*/
		public static bool LoadNavMesh(Sample_SoloMesh sample, string path)
		{
			if (null == sample)
				return false;
			return sample.loadNavmesh(path);
		}

		/*加载已经烘焙好的网格文件.bin --兼容web平台*/
		public static bool LoadNavMesh(Sample_SoloMesh sample, byte[] navMeshData)
		{
			if (null == sample)
				return false;
			return sample.loadNavmesh(navMeshData);
		}

		/*保存已经烘焙好的网格文件.bin*/
		public static void SaveNavMesh(Sample_SoloMesh sample, string path)
		{
			if (null == sample)
				return;
			sample.saveAll(path);
		}

		/*构建寻路网格*/
		public static bool Build(Sample_SoloMesh sample)
		{
			if (null == sample)
				return false;
			return sample.Build();
		}

		/*查找直线路径*/
		public static void FindPathStraight(Sample_SoloMesh sample, float* m_spos, float* m_epos, float* outPoints, out int outPointCount, dtStraightPathOptions m_straightPathOptions)
		{
			outPointCount = 0;
			if (null == sample)
				return;
			sample.findPathStraight(m_spos, m_epos, outPoints, ref outPointCount, m_straightPathOptions);
		}

		/*查找跟随路径 -- 当出现坡度时不是直线行走, 而是先上坡再下坡*/
		public static void FindPathFollow(Sample_SoloMesh sample, float* m_spos, float* m_epos, float* outPoints, out int outPointCount, dtStraightPathOptions m_straightPathOptions)
		{
			outPointCount = 0;
			if (null == sample)
				return;
			sample.findPathFollow(m_spos, m_epos, outPoints, ref outPointCount, m_straightPathOptions);
		}

		/*释放寻路网格实例*/
		public static void FreeSoloMesh(Sample_SoloMesh sample)
		{
			if (null == sample)
				return;
			//rcDelete(sample);
			//RemoveObjHook(sample);
		}

		/*获取绘制烘焙网格*/
		public static void GetDrawNavMesh(Sample_SoloMesh sample, float* vertsArray, out int vertsCount)
		{
			vertsCount = 0;
			if (null == sample)
				return;
			sample.getDrawNavMesh(vertsArray, ref vertsCount);
		}

		/*获取绘制烘焙网格顶点长度*/
		public static int GetDrawNavMeshCount(Sample_SoloMesh sample)
		{
			if (null == sample)
				return -1;
			return sample.getDrawNavMeshCount();
		}
	}
}